#pragma once

#ifndef Utilities_h
#define Utilities_h

#include <algorithm>
#include <vector>
#include <unordered_map>

#include "Type.h"
#include "TreeNode.h"
#include "Clause.h"
#include "RelationshipArgumentType.h"

class Utilities{

public:
	static bool isValidLineIndex(Type::Index::Line);
	static bool isValidProcIndex(Type::Index::Proc);
	static bool isValidProcName(Type::Name::Proc);
	static bool isValidVarIndex(Type::Index::Var);
	static bool isValidVarName(Type::Name::Var);
	static bool isValidConstant(Type::Constant);
	static bool isValidNode(TreeNode*);
	static bool isValidLineEntity(Type::Entity::Line);
	static bool isValidBitMap(Type::Map::Bit&);
	static bool isValidLineMatrix(Type::Map::LineMatrix&);
	static bool isvalidParentMatrix(Type::Map::ParentMatrix&);
	static bool isValidStartEnd(Type::Index::Line, Type::Index::Line);

	static unsigned int getIndexBoundedBySize(unsigned int, unsigned int);

	template<typename T> 
	static bool isExistInList(const std::vector<T> &list, T element){
		if(find(list.begin(),list.end(),element)!= list.end()){
			return true;
		}else{
			return false;
		}
	}

	template<typename T>
	static vector<T> removeElementFromList(const std::vector<T> &list, T element, bool removeAll){
		bool quit = false;
		std::vector<T> resultList = list;
		std::vector<T>::iterator it;

		while(resultList.size() > 0 && !quit){
			it = find(resultList.begin(), resultList.end(), element);
			if(it != resultList.end()){
				resultList.erase(it);
				quit = !removeAll;
			}else{
				quit = true;
			}
		}
		return resultList;
	}

	template<typename T>
	static std::vector<T> copyListWithoutDuplicates(const std::vector<T> &list){
		unsigned int i;
		std::vector<T> resultList;
		T element;
		for(i = 0; i< list.size(); i++){
			element = list.at(i);

			if(find(resultList.begin(), resultList.end(), element) == resultList.end()){
				resultList.push_back(element);
			}
		}
		return resultList;
	}

	template<typename T_Source, typename T_Copy>
	static std::vector<T_Copy> copyListWithValueInSource(const std::vector<T_Source> &sourceList, T_Source sourceValue, 
		std::vector<T_Copy> copyList){

			unsigned int i;
			std::vector<T_Copy> resultList;
			T_Source sourceElement;
			T_Copy copyElement;
			for(i = 0; i < sourceList.size() && sourceList.size() == copyList.size(); i++){
				sourceElement = sourceList.at(i);
				copyElement = copyList.at(i);

				if(sourceElement == sourceValue && 
					find(resultList.begin(), resultList.end(), copyElement) == resultList.end()){

						resultList.push_back(copyElement);
				}
			}
			return resultList;
	}

	template<typename T>
	static std::vector<T> mergeListWithIntersection(const std::vector<T> &list1, const std::vector<T> &list2){

		unsigned int i, j;
		std::vector<T> resultList;
		T element1, element2;
		for(i = 0; i < list1.size(); i++){
			element1 = list1.at(i);

			for(j = 0; j < list2.size(); j++){
				element2 = list2.at(j);

				if(element1 == element2 && 
					find(resultList.begin(), resultList.end(), element1) == resultList.end()){

						resultList.push_back(element1);
				}
			}
		}
		return resultList;
	}

	template <typename T>
	bool isListExactMatch(const std::vector<T> &list1, const std::vector<T> &list2){
		if(list1.size() != list2.size()){
			return false;
		}

		for(std::size_t i=0; i<list1.size(); i++){
			for(std::size_t n=0; n<list2.size(); n++){
				if(list1.at(i) == list2.at(n)){
					list1.erase(list1.begin()+i);
					list2.erase(list2.begin()+n);
					i--;
					n--;
					break;
				}
			}
		}
		return list1.size() == 0 && list2.size() == 0;
	}

	template<typename T, class C>
	static bool isKeyInMap_TypeClass(const std::map<T, C> &map, T key) {
		if (map.find(key) != map.end()) {
			return true;
		} else {
			return false;
		}
	}

	template<typename T, class C>
	static bool isKeyInMap_TypeClass(const std::unordered_map<T, C> &map, T key) {
		if (map.find(key) != map.end()) {
			return true;
		} else {
			return false;
		}
	}

	template<class C, typename T>
	static bool isKeyInMap_ClassType(const std::map<C, T> &map, C key) {
		if (map.find(key) != map.end()) {
			return true;
		} else {
			return false;
		}
	}

	template<class C, typename T>
	static bool isKeyInMap_ClassType(const std::unordered_map<C, T> &map, C key) {
		if (map.find(key) != map.end()) {
			return true;
		} else {
			return false;
		}
	}

	template<typename T1, typename T2>
	static bool isKeyInMap_TypeType(const std::map<T1, T2> &map, T1 key) {
		if (map.find(key) != map.end()) {
			return true;
		} else {
			return false;
		}
	}

	template<typename T1, typename T2>
	static bool isKeyInMap_TypeType(const std::unordered_map<T1, T2> &map, T1 key) {
		if (map.find(key) != map.end()) {
			return true;
		} else {
			return false;
		}
	}

	static bool isSynonymTypeRelationshipArgument(RelationshipArgumentType relArgType) {
		switch (relArgType) {
		case INTEGER:
			// Fallthrough
		case INVALID_ARG:
			// Fallthrough
		case STRING:
			// Fallthrough
		case UNDERSCORE:
			return false;
			break;
		default:
			return true;
			break;
		}
	}

	/// Returns the string that represents the clause.
	static std::string clauseToString(const Clause &clause) {
		std::string clauseString;
		switch (clause.type) {
		case Clause::TYPE_RELATIONSHIP_CLAUSE:
			clauseString = "Relationship: " + entryToString(*clause.relationshipEntry);
			break;
		case Clause::TYPE_PATTERN_CLAUSE:
			clauseString = "Pattern: " + entryToString(*clause.patternEntry);
			break;
		default: // WITH_CLAUSE
			clauseString = "With: " + entryToString(*clause.constraintEntry);
			break;
		}
		return clauseString;
	}

	/// Returns the string that represents the relationship
	static std::string entryToString(const RelationshipEntry &relEntry) {
		RelationshipType relType = relEntry.relationshipType;
		const std::string &arg1 = relEntry.relationshipArguments[0];
		const std::string &arg2 = relEntry.relationshipArguments[1];

		std::string entryString;
		switch (relType) {
		case AFFECTS:
			entryString = "Affects";
			break;
		case AFFECTS_STAR:
			entryString = "Affects*";
			break;
		case AFFECTSBIP:
			entryString = "AffectsBIP";
			break;
		case AFFECTSBIP_STAR:
			entryString = "AffectsBIP*";
			break;
		case CALLS:
			entryString = "Calls";
			break;
		case CALLS_STAR:
			entryString = "Calls*";
			break;
		case CONTAINS:
			entryString = "Contains";
			break;
		case CONTAINS_STAR:
			entryString = "Contains*";
			break;
		case SIBLING:
			entryString = "Sibling";
			break;
		case FOLLOWS:
			entryString = "Follows";
			break;
		case FOLLOWS_STAR:
			entryString = "Follows*";
			break;
		case MODIFIES_LINE:
			// Fallthrough
		case MODIFIES_PROC:
			entryString = "Modifies";
			break;
		case NEXT:
			entryString = "Next";
			break;
		case NEXT_STAR:
			entryString = "Next*";
			break;
		case NEXTBIP:
			entryString = "NextBIP";
			break;
		case NEXTBIP_STAR:
			entryString = "NextBIP*";
			break;
		case PARENT:
			entryString = "Parent";
			break;
		case PARENT_STAR:
			entryString = "Parent*";
			break;
		case USES_LINE:
			// Fallthrough
		case USES_PROC:
			entryString = "Uses";
			break;
		default: // INVALID for now
			entryString = "INVALID RELATIONSHIP";
			return entryString;
			break;
		}
		
		entryString += "("; // Relationship(
		entryString += arg1 + ","; // Relationship(arg1,
		entryString += arg2 + ")"; // Relationship(arg1,arg2)

		return entryString;
	}

	/// Returns the string that represents the pattern
	static std::string entryToString(const PatternEntry &patEntry) {
		PatternType patType = patEntry.patternType;
		const std::string &patternSynonym = patEntry.synonym;
		const std::string &arg1 = patEntry.args[0];
		const std::string &arg2 = patEntry.args[1];
		const std::string &arg3 = patEntry.args[2];
		
		std::string entryString;
		switch (patType) {
		case PATTERN_ASSIGN:
			entryString = "PATTERN_ASSIGN " + patternSynonym + "(" + arg1 + "," + arg2 + ")";
			break;
		case PATTERN_IF:
			entryString = "PATTERN_IF " + patternSynonym + "(" + arg1 + "," + arg2 + "," + arg3 + ")";
			break;
		case PATTERN_WHILE:
			entryString = "PATTERN_WHILE " + patternSynonym + "(" + arg1 + "," + arg2 + ")";
			break;
		default: // INVALID
			entryString = "INVALID PATTERN";
			break;
		}
		return entryString;
	}

	/// Returns the string that represents the with constraint
	static std::string entryToString(const ConstraintEntry &conEntry) {
		ConstraintType conType = conEntry.constraintType;
		const std::string &arg1 = conEntry.constraintArguments[0];
		ConstraintAttrType attrType1 = conEntry.constraintAttrTypes[0];
		const std::string &arg2 = conEntry.constraintArguments[1];
		ConstraintAttrType attrType2 = conEntry.constraintAttrTypes[1];

		std::string entryString;
		switch (conType) {
		case WITH_STRING:
			entryString = "WITH_STRING ";
			break;
		case WITH_INTEGER:
			entryString = "WITH_INTEGER ";
			break;
		default: // INVALID
			entryString = "INVALID WITH";
			return entryString;
			break;
		}

		entryString += arg1; // with arg1
		
		switch (attrType1) {
		case NONE:
			break;
		case PROC_NAME:
			entryString += ".procName";
			break;
		case STMT_INDEX:
			entryString += ".stmt#";
			break;
		case VALUE:
			entryString += ".value";
			break;
		case VAR_NAME:
			entryString += ".varName";
			break;
		default: // INVALID
			entryString += ".INVALID";
			break;
		}

		entryString += " = " + arg2; // with arg1.attr = arg2

		switch (attrType2) {
		case NONE:
			break;
		case PROC_NAME:
			entryString += ".procName";
			break;
		case STMT_INDEX:
			entryString += ".stmt#";
			break;
		case VALUE:
			entryString += ".value";
			break;
		case VAR_NAME:
			entryString += ".varName";
			break;
		default: // INVALID
			entryString += ".INVALID";
			break;
		}

		return entryString;
	}
};
#endif